import Particle from './Particle'
import Attractor from './Attractor'
import Mouse from './Mouse'
import Canvas from './Canvas'
import Perlin from '@/common/plugins/js/Perlin'
import Vector2 from '@/common/plugins/js/Vector2'

export default class ParticleSystem {
  tick = 0

  RAND = Math.random
  SIN = Math.sin
  COS = Math.cos
  ABS = Math.abs
  PI = Math.PI

  frame = 0

  constructor(
    selector,
    config = {
      fill: 'rgba(0,0,0,0.175)',
      size: 2,
      speed: { x: 5, y: -5 },
      maxCount: 4000,
      mouseThreshold: 200,
      mousePower: 40,
      attractorThreshold: 300,
      attractorPower: 60,
      attractorSize: 12,
      attractorColor: 'rgba(10,10,10,0.4)',
      maxAttractors: 12,
    }
  ) {
    this.tick = 0
    this.particles = []
    this.attractors = []
    this.mouse = new Mouse()
    this.canvas = new Canvas(selector, '2d')
    this.bounds = this.canvas.dimensions
    this.config = config

    this.noise = new Perlin()
  }

  start() {
    const self = this
    self.update()
    this.frame = window.requestAnimationFrame(self.start.bind(self))
  }

  pause() {
    window.cancelAnimationFrame(this.frame)
  }

  update() {
    let colorString = ''
    if (this.mouse.dblclick) {
      let remove = false
      for (let j = this.attractors.length - 1; j >= 0; j--) {
        const a = this.attractors[j]
        if (this.mouse.position.distanceTo(a.position) < a.size) {
          this.attractors.splice(j, 1)
          remove = true
        }
      }
      if (!remove && this.attractors.length < this.config.maxAttractors) {
        this.attractors.push(new Attractor(this.mouse.position.x, this.mouse.position.y))
      }
      this.mouse.dblclick = false
    }
    this.canvas.fill(0, 0, this.canvas.dimensions.x, this.canvas.dimensions.y, this.config.fill)
    for (let i = 3; i > 0; i--) {
      const p = new Particle(
        this.RAND() * this.canvas.dimensions.x,
        this.RAND() * this.canvas.dimensions.y
      )
      this.particles.push(p)
    }
    for (let i = this.particles.length - 1; i >= 0; i--) {
      const p = this.particles[i]
      this.checkBounds(p)
      if (p.remove === true || this.particles.length > this.config.maxCount) {
        this.particles.splice(i, 1)
      } else {
        p.lastPosition.x = p.position.x
        p.lastPosition.y = p.position.y
        const noiseVal = this.noise.simplex3(
          p.position.x * 0.0015 * ((this.mouse.position.x / this.bounds.x) * 0.7 + 0.1),
          p.position.y * 0.0025 * ((this.mouse.position.y / this.bounds.y) * 0.7 + 0.1),
          this.tick * 0.0015
        )
        const noiseNorm = this.ABS(noiseVal)
        const theta = noiseVal * this.PI
        const vel = new Vector2(
          this.COS(theta) * this.config.speed.x,
          this.SIN(theta) * this.config.speed.y
        )
        const hue = 90 * noiseNorm + 160
        colorString = this.canvas.hsla(hue, '70%', '50%', 1)
        p.velocity.lerp(vel)
        p.position.add(p.velocity)
        if (
          this.mouse.hover &&
          this.mouse.position.distanceTo(p.position) < this.config.mouseThreshold
        ) {
          if (this.mouse.mousedown && this.config.mousePower > 0) {
            this.config.mousePower *= -1
          } else if (!this.mouse.mousedown && this.config.mousePower < 0) {
            this.config.mousePower = this.ABS(this.config.mousePower)
          }
          const mTheta = this.mouse.position.angleTo(p.position)
          const mVel = new Vector2(
            this.COS(mTheta) * this.config.mousePower,
            this.SIN(mTheta) * this.config.mousePower
          )
          p.velocity.lerp(mVel, 0.0175)
        }
        for (let j = this.attractors.length - 1; j >= 0; j--) {
          const a = this.attractors[j]
          const pDist = p.position.distanceTo(a.position)
          if (pDist < this.config.attractorThreshold) {
            if (pDist < 2) {
              this.particles.splice(i, 1)
              continue
            } else {
              const aTheta = a.position.angleTo(p.position)
              const aVel = new Vector2(
                this.COS(aTheta) * -this.config.attractorPower,
                this.SIN(aTheta) * -this.config.attractorPower
              )
              p.velocity.lerp(aVel, 0.0175)
            }
          }
        }
        this.canvas.ctx.save()
        this.canvas.ctx.globalCompositeOperation = 'lighter'
        this.canvas.drawLine(
          p.position.x,
          p.position.y,
          p.lastPosition.x,
          p.lastPosition.y,
          colorString,
          this.config.size
        )
        this.canvas.ctx.restore()
      }
    }
    for (let j = this.attractors.length - 1; j >= 0; j--) {
      const a = this.attractors[j]
      if (a.size < this.config.attractorSize) {
        a.size += 0.1
      }
      this.canvas.ctx.save()
      this.canvas.ctx.shadowBlur = 8
      this.canvas.ctx.shadowColor = colorString
      this.canvas.drawArc(a.position.x, a.position.y, a.size, this.config.attractorColor)
      this.canvas.ctx.restore()
    }
    this.tick++
  }

  checkBounds(p) {
    if (
      p.position.x > this.bounds.x ||
      p.position.x < -this.config.size ||
      p.position.y > this.bounds.y ||
      p.position.y < -this.config.size
    ) {
      p.remove = true
    }
  }
}
